﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Configuration;
using System.Xml;
using System.Xml.XPath;

namespace Landscape {
    /// <summary>
    /// Output methods supported by this library
    /// </summary>
    public enum OutputMethods {
        /// <summary>
        /// Outputs content as a parsable XML document
        /// </summary>
        xml,
        /// <summary>
        /// Outputs content as a parsable XHTML document
        /// </summary>
        xhtml
    }

    /// <summary>
    /// Represents the configuration context into which Landscape is running
    /// </summary>
    public class Context {
        /// <summary>
        /// The xmlns used by landscape configuration files
        /// </summary>
        private const string configNamespace = "urn:landscape-config";

        #region static settings
        /// <summary>
        /// Toggles global caching
        /// </summary>
        public static bool enableCaching {
            get {
                string param = GetSetting("landscapeEnableCaching");
                if(System.Web.HttpRuntime.Cache != null){
                    return !string.IsNullOrEmpty(param)? param.ToLower() != "false": true;
                }

                return false;
            }
        }

        /// <summary>
        /// The virtual path to Landscape configuration file
        /// </summary>
        public static string configFileVirtualPath {
            get {
                return GetSetting("landscapeConfigFileVirtualPath") ?? "~/landscape.xml";
            }
        }

        /// <summary>
        /// The XSL extension, ASP.NET style with trailing dot
        /// </summary>
        public static string xslExtension {
            get {
                return GetSetting("landscapeXslExtension") ?? ".xslt";
            }
        }

        /// <summary>
        /// The XSLT namespace to use to create XSLT elements runtime
        /// </summary>
        public const string xslNamespace = "http://www.w3.org/1999/XSL/Transform";

        /// <summary>
        /// The XSLT version used by the framework
        /// </summary>
        public const string xslVersion = "1.0";

        /// <summary>
        /// The namespace used to create Landscape elements
        /// </summary>
        internal const string lscNamespace = "urn:landscape-xsl";

        /// <summary>
        /// The specific public DocType used by master templates (wrappers)
        /// </summary>
        public static string outputDocTypePublic{
            get{
                return GetSetting("landscapeOutputDocTypePublic") ?? "-//W3C//DTD XHTML 1.0 Transitional//EN";
            }
        }
        /// <summary>
        /// The specific system DocType used by master templates (wrappers)
        /// </summary>
        public static string outputDocTypeSystem {
            get {
                return GetSetting("landscapeOutputDocTypeSystem") ?? "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd";
            }
        }

        /// <summary>
        /// The Landscape.OutputMethods used by Landscape
        /// </summary>
        public static OutputMethods xslOutputMethod{
            get {
                string param = GetSetting("landscapeOutputMethod");
                if(!string.IsNullOrEmpty(param)){
                    OutputMethods result;
                    if (Enum.TryParse<Landscape.OutputMethods>(param, out result)) {
                        return result;
                    }            
                }
                return OutputMethods.xhtml;
            }
        }

        /// <summary>
        /// Wether to indent output of transformations
        /// </summary>
        public static bool outputIndent {
            get {
                string param = GetSetting("landscapeOutputIndent");
                bool result = false;
                return !string.IsNullOrEmpty(param) && Boolean.TryParse(param, out result)? result: true;
            }
        }

        private static string GetSetting(string paramName) {
            if (ConfigurationManager.AppSettings != null) {
                string param = ConfigurationManager.AppSettings[paramName];
                if (!string.IsNullOrEmpty(param)) return param;
            }

            return null;
        }

        private static Context _Instance;
        /// <summary>
        /// The current instance of configuration context
        /// </summary>
        public static Context Instance {
            get {
                if (_Instance == null) {
                    string configPath = Globals.AbsPath(Context.configFileVirtualPath);
                    bool useCache = Context.enableCaching &&
                                    !string.IsNullOrEmpty(Context.configFileVirtualPath) &&
                                    System.IO.File.Exists(configPath);
                    string cacheName = typeof(Landscape.Context).FullName + "@" + Context.configFileVirtualPath;
                    if (useCache && System.Web.HttpRuntime.Cache[cacheName] != null) {
                        try {
                            _Instance = (Context)System.Web.HttpRuntime.Cache[cacheName];
                            return _Instance;
                        } catch {
                            ///TODO: management of corrupted cache
                        }
                    }

                    _Instance = new Context(configPath);
                    if (useCache) {
                        System.Web.HttpRuntime.Cache.Add(cacheName,
                                                         _Instance,
                                                         new System.Web.Caching.CacheDependency(Globals.AbsPath(Context.configFileVirtualPath)),
                                                         DateTime.Now.AddYears(1),
                                                         new TimeSpan(),
                                                         System.Web.Caching.CacheItemPriority.Normal,
                                                         null);
                    }
                }

                return _Instance;
            }
        }
        #endregion

        private Dictionary<int, string> _templatePaths = null;
        /// <summary>
        /// A list of paths used for xslt templates lookup, ordered by priority.
        /// </summary>
        public Dictionary<int, string> templatePaths {
            get {
                return _templatePaths != null ? _templatePaths : new Dictionary<int, string>();
            }
        }

        private Dictionary<int, string> _templateVirtualPaths = null;
        /// <summary>
        /// A list of virtual paths used for xslt templates lookup, ordered by priority.
        /// This setting is required by Landscape.MVC since ViewEngine uses virtual paths
        /// </summary>
        public Dictionary<int, string> templateVirtualPaths {
            get {
                return _templateVirtualPaths != null ? _templateVirtualPaths : new Dictionary<int, string>();
            }
        }

        private Dictionary<int, string> _includePaths = null;
        /// <summary>
        /// A list of paths used for xsl:include/import lookup, ordered by priority.
        /// </summary>
        public Dictionary<int, string> includePaths {
            get {
                return _includePaths != null ? _includePaths : new Dictionary<int, string>();
            }
        }

        private Dictionary<int, string> _widgetPaths = null;
        /// <summary>
        /// A list of paths used for xslt widgets lookup, ordered by priority.
        /// </summary>
        public Dictionary<int, string> widgetPaths {
            get {
                return _widgetPaths != null ? _widgetPaths : new Dictionary<int, string>();
            }
        }

        private Dictionary<int, string> _controlPaths = null;
        /// <summary>
        /// A list of virtual paths used for asp.net usercontrols lookup, ordered by priority.
        /// </summary>
        public Dictionary<int, string> controlPaths {
            get {
                return _controlPaths != null ? _controlPaths : new Dictionary<int, string>();
            }
        }

        private Dictionary<string, System.Type> _xsltExtensions = null;
        /// <summary>
        /// A list of paths used for xslt widgets lookup, ordered by priority.
        /// </summary>
        public Dictionary<string, System.Type> xsltExtensions {
            get {
                return _xsltExtensions != null ? _xsltExtensions : new Dictionary<string, System.Type>();
            }
        }

        private IXSLCompiler _compiler = null;
        public IXSLCompiler compiler {
            get {
                return _compiler;
            }
        }

        /// <summary>
        /// Creates a new instance of landscape settings using the supplied path to a validxml configuration
        /// </summary>
        /// <param name="configPath">The path of the configuration xml document</param>
        public Context(string configPath) {
            if (System.IO.File.Exists(configPath)) {
                string configSchema = Resources.GetEmbeddedResource("Landscape.Schemas.landscapeConfig.xsd");

                _templatePaths = new Dictionary<int, string>();
                _templateVirtualPaths = new Dictionary<int, string>();
                _includePaths = new Dictionary<int, string>();
                _widgetPaths = new Dictionary<int, string>();
                _controlPaths = new Dictionary<int, string>();
                _xsltExtensions = new Dictionary<string,Type>();

                var doc = new XmlDocument();
                doc.LoadTrustedXml(configNamespace,
                                    new System.IO.StringReader(System.IO.File.ReadAllText(configPath)),
                                    new System.IO.StringReader(configSchema));

                XPathNavigator root = doc.CreateNavigator();

                XPathNavigator compiler = root.SelectSingleNode("child::*[local-name() = 'compiler']");

                if (compiler != null) {
                    string compAsm = compiler.GetAttribute("assembly", "");
                    string compType = compiler.GetAttribute("type", "");
                    Type compilerType = Type.GetType(compType + ", " + compAsm);
                    _compiler = (IXSLCompiler)Activator.CreateInstance(compilerType, new object[] { });
                } else {
                    _compiler = new Compilers.DotNetCompiler();
                }

                XPathExpression expr = root.Compile("child::*[local-name() = 'landscape']/child::*[local-name() = 'templates']/child::*[local-name() = 'add']");
                expr.AddSort("@priority", XmlSortOrder.Descending, XmlCaseOrder.None, string.Empty, XmlDataType.Number);
                XPathNodeIterator templates = root.Select(expr);
                while (templates.MoveNext()) {
                    int priority;
                    Int32.TryParse(templates.Current.GetAttribute("priority", ""), out priority);
                    string virtualPath = "~" + templates.Current.GetAttribute("path", "");
                    string physicalPath = Globals.AbsPath(templates.Current.GetAttribute("path", ""));
                    if (System.IO.Directory.Exists(physicalPath)) {
                        _templatePaths.Add(priority, physicalPath);
                        _templateVirtualPaths.Add(priority, virtualPath);
                    }
                }


                expr = root.Compile("child::*[local-name() = 'landscape']/child::*[local-name() = 'includes']/child::*[local-name() = 'add']");
                expr.AddSort("@priority", XmlSortOrder.Descending, XmlCaseOrder.None, string.Empty, XmlDataType.Number);
                XPathNodeIterator includes = root.Select(expr);
                while (includes.MoveNext()) {
                    int priority;
                    Int32.TryParse(includes.Current.GetAttribute("priority", ""), out priority);
                    string path = Globals.AbsPath(includes.Current.GetAttribute("path", ""));
                    if (System.IO.Directory.Exists(path)) {
                        _includePaths.Add(priority, path);
                    }
                }

                expr = root.Compile("child::*[local-name() = 'landscape']/child::*[local-name() = 'widgets']/child::*[local-name() = 'add']");
                expr.AddSort("@priority", XmlSortOrder.Descending, XmlCaseOrder.None, string.Empty, XmlDataType.Number);
                XPathNodeIterator widgets = root.Select(expr);
                while (widgets.MoveNext()) {
                    int priority;
                    Int32.TryParse(widgets.Current.GetAttribute("priority", ""), out priority);
                    string path = Globals.AbsPath(widgets.Current.GetAttribute("path", ""));
                    if (System.IO.Directory.Exists(path)) {
                        _widgetPaths.Add(priority, path);
                    }
                }

                expr = root.Compile("child::*[local-name() = 'landscape']/child::*[local-name() = 'controls']/child::*[local-name() = 'add']");
                expr.AddSort("@priority", XmlSortOrder.Descending, XmlCaseOrder.None, string.Empty, XmlDataType.Number);
                XPathNodeIterator controls = root.Select(expr);
                while (controls.MoveNext()) {
                    int priority;
                    Int32.TryParse(controls.Current.GetAttribute("priority", ""), out priority);
                    string path = controls.Current.GetAttribute("virtualpath", "");
                    if (System.IO.Directory.Exists(Globals.AbsPath(path))) {
                        _controlPaths.Add(priority, path);
                    }
                }

                expr = root.Compile("child::*[local-name() = 'landscape']/child::*[local-name() = 'extensions']/child::*[local-name() = 'add']");
                expr.AddSort("@priority", XmlSortOrder.Descending, XmlCaseOrder.None, string.Empty, XmlDataType.Number);
                XPathNodeIterator extensions = root.Select(expr);
                while (extensions.MoveNext()) {
                    string alias = extensions.Current.GetAttribute("alias", "");
                    string assembly = extensions.Current.GetAttribute("assembly", "");
                    string type = extensions.Current.GetAttribute("type", "");
                    Type extType = Type.GetType(type + ", " + assembly);
                    _xsltExtensions.Add(alias, extType);
                }
            }
        }
    }
}
